package org.saxion.barbershop;

import java.util.Random;
import java.util.concurrent.Semaphore;

class ThreadEnvironment extends Thread {
	static Semaphore statusLocker;
	static Semaphore wakeUp;
	static Semaphore developerRequest;
	static Semaphore customerRequest;
	static Semaphore readyForMeeting, signalToStartMeeting,
			endDeveloperMeeting;
	static Semaphore startCustomerMeeting, endCustomerMeeting;

	private Semaphore printLock;

	static int amountOfDeveloperRequests = 0;
	static int amountOfCustomerRequests = 0;
	static boolean busy, thereIsADeveloperMeeting;
	static final int NR_OF_DEVELOPERS = 7;
	static final int NR_OF_CUSTOMERS = 15;
	static final int DEVELOPER_PAIR = 2;

	public ThreadEnvironment() {
		statusLocker = new Semaphore(1);
		wakeUp = new Semaphore(0);
		developerRequest = new Semaphore(1);
		customerRequest = new Semaphore(1);
		readyForMeeting = new Semaphore(0);
		signalToStartMeeting = new Semaphore(0);
		endDeveloperMeeting = new Semaphore(0);
		startCustomerMeeting = new Semaphore(0);
		endCustomerMeeting = new Semaphore(0);
		printLock = new Semaphore(1);

		busy = false;
		thereIsADeveloperMeeting = false;

		Leader leader = new Leader();
		leader.start();
		for (int i = 1; i <= NR_OF_DEVELOPERS; i++) {
			Developer developer = new Developer();
			developer.start();
		}
		for (int i = 1; i <= NR_OF_CUSTOMERS; i++) {
			Customer customer = new Customer();
			customer.start();
		}

	}

	public void print(String message) {
		printLock.acquireUninterruptibly();
		System.out.println(message);
		printLock.release();
	}

	@SuppressWarnings("static-access")
	private void busyTime(int randomTime, Thread thread) {
		try {
			int busyTime = (new Random().nextInt(randomTime)+1)*1000;
			thread.sleep(busyTime);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	class Leader extends Thread {

		public void run() {
			while (true) {
				meetingRoutine();
			}
		}

		private void meetingRoutine() {
			wakeUp.acquireUninterruptibly();
			print("Leader checks if there is need for a customer meeting");
			if (customerMeetingIsNeeded()) {
				print("Leader sees that there are " + amountOfCustomerRequests
						+ " customers waiting for a meeting");
				customerMeetingRoutine();
			}
			print("Leader checks if there is need for a developer meeting");
			if (developerMeetingIsNeeded()) {
				print("Leader sees that there are " + amountOfDeveloperRequests
						+ " developers waiting for a meeting");
				developerMeetingRoutine();
			}
		}

		private void customerMeetingRoutine() {
			setStatusToBusy();
			print("There are " + amountOfDeveloperRequests + " developers waiting"
					+ " when the leader wants to start a customer meeting");
			sendWaitingDevelopersAway();
			print("Leader sent waiting developers away, there are now "
					+ amountOfDeveloperRequests + " developers waiting");
			wakeUp.drainPermits();
			startCustomerMeeting.acquireUninterruptibly(NR_OF_CUSTOMERS);
			print("Leader notifies he wants to start the meeting with the customers");
			busyTime(4, this);
			endCustomerMeeting.release(NR_OF_CUSTOMERS);
			print("Leader notifies he ends the meeting with the customers");
			setStatusToNotBusy();
		}
		
		private void sendWaitingDevelopersAway() {
			developerRequest.acquireUninterruptibly();
			amountOfDeveloperRequests = 0;
			developerRequest.release();
		}

		private void developerMeetingRoutine() {
			print("Leader notifies that he wants to start a developper meeting");
			readyForMeeting.release(DEVELOPER_PAIR);
			signalToStartMeeting.acquireUninterruptibly(DEVELOPER_PAIR);
			pickDeveloperPair();
			print("Leader has been notified that the developers received his signal");
			busyTime(3, this);
			endDeveloperMeeting.release(DEVELOPER_PAIR);
			print("Leader notifies that the developper meeting has ended");
		}

		private void pickDeveloperPair() {
			developerRequest.acquireUninterruptibly();
			amountOfDeveloperRequests -= 2;
			developerRequest.release();
		}

		private boolean customerMeetingIsNeeded() {
			customerRequest.acquireUninterruptibly();
			if (amountOfCustomerRequests == NR_OF_CUSTOMERS) {
				print("there are " + amountOfCustomerRequests
						+ " customers waiting");
				customerRequest.release();
				return true;
			}
			customerRequest.release();
			return false;
		}

		private boolean developerMeetingIsNeeded() {
			developerRequest.acquireUninterruptibly();
			if (amountOfDeveloperRequests >= DEVELOPER_PAIR) {
				developerRequest.release();
				print("there are " + amountOfDeveloperRequests
						+ " developers waiting");
				return true;
			}
			developerRequest.release();
			return false;
		}

		private void setStatusToBusy() {
			statusLocker.acquireUninterruptibly();
			busy = true;
			print("Leader status is set on busy");
			statusLocker.release();
		}

		private void setStatusToNotBusy() {
			statusLocker.acquireUninterruptibly();
			busy = false;
			print("Leader status is set on available");
			statusLocker.release();
		}
		
	}

	class Customer extends Thread {

		public void run() {
			while (true) {
				customerRoutine();
			}
		}

		private void customerRoutine() {
			busyTime(5, this);
			takeASeatForMeeting();
			startCustomerMeeting.release();
			endCustomerMeeting.acquireUninterruptibly();
			leaveSeat();
		}

		private void takeASeatForMeeting() {
			customerRequest.acquireUninterruptibly();
			amountOfCustomerRequests++;
			print("customer takes a seat");
			if (amountOfCustomerRequests == NR_OF_CUSTOMERS) {
				wakeUp.release();
			}
			customerRequest.release();
		}

		private void leaveSeat() {
			customerRequest.acquireUninterruptibly();
			amountOfCustomerRequests--;
			customerRequest.release();
		}

	}

	class Developer extends Thread {

		@Override
		public void run() {
			while (true) {
				developerRoutine();
			}
		}

		private void developerRoutine() {
			busyTime(4, this);
			if (bossIsAvailable()) {
				takeSeat();
				while (bossIsAvailable()) {
					requestForMeeting();
				}
			}
		}

		private boolean bossIsAvailable() {
			statusLocker.acquireUninterruptibly();
			if (busy) {
				statusLocker.release();
				return false;
			}
			statusLocker.release();
			return true;
		}

		private void requestForMeeting() {
			if (readyForMeeting.tryAcquire()) {
				signalToStartMeeting.release();
				endDeveloperMeeting.acquireUninterruptibly();
			}
		}

		private void takeSeat() {
			developerRequest.acquireUninterruptibly();
			amountOfDeveloperRequests++;
			if (amountOfDeveloperRequests % DEVELOPER_PAIR == 0) {
				wakeUp.release();
			}
			developerRequest.release();
		}
	}
}
